#import "DetailViewController+DragDrop.h"
#import "RNConstants.h"
#import "NSManagedObject+SRAdditions.h"
#import "NSManagedObjectContext+SRAdditions.h"
#import "NSTreeController+SRAdditions.h"
#import "RNDetailHeader.h"
#import "RNListItem.h"
#import "RNList.h"
#import "RNStore.h"
#import "RNAisle.h"
#import "RNHistoryItem.h"
#import "RNDetailView.h"


@implementation DetailViewController (DragDropAndCopyPaste)

-(void)copyNodes:(NSArray *)nodes toPasteboard:(NSPasteboard *)pasteboard {
    NSMutableDictionary *objectArraysByType = [NSMutableDictionary dictionary];
    for (NSTreeNode *node in nodes) {
        NSManagedObject *object = [node representedObject];
        NSString *entityName = [object entityName];
        NSMutableArray *typeArray = [objectArraysByType valueForKey:entityName];
        if (!typeArray) {
            typeArray = [NSMutableArray array];
            [objectArraysByType setValue:typeArray forKey:entityName];
        }
        NSMutableDictionary *dictionaryRepresentation = [NSMutableDictionary dictionaryWithDictionary:[object valueForKey:RNDictionaryRepresentationKey]];
        [dictionaryRepresentation setValue:[object URIRepresentation] forKey:RNDragDropObjectURIKey];
//        NSInteger rowIndex = [[self detailView] rowForItem:node];
//        [dictionaryRepresentation setValue:[NSNumber numberWithInteger:rowIndex] forKey:RNDragDropRowKey];
        [typeArray addObject:dictionaryRepresentation];
    }
    
    NSPasteboardItem *pasteboardItem = [[NSPasteboardItem alloc] init];
    NSData *pasteboardData = [NSKeyedArchiver archivedDataWithRootObject:objectArraysByType];
    [pasteboardItem setData:pasteboardData forType:RNDragDropUTI];
    [pasteboard clearContents]; 
    [pasteboard writeObjects:[NSArray arrayWithObject:pasteboardItem]];
}

-(NSDictionary *)objectArraysFromPasteboard:(NSPasteboard *)pasteboard {
    NSArray *pasteboardItems = [pasteboard pasteboardItems];
    if ([pasteboardItems count] != 1)
        return nil;
    NSPasteboardItem *pasteboardItem = [pasteboardItems objectAtIndex:0];
    NSData *pasteboardData = [pasteboardItem dataForType:RNDragDropUTI];
    return [NSKeyedUnarchiver unarchiveObjectWithData:pasteboardData];
}


#pragma mark -
#pragma mark Drag/Drop

- (BOOL)outlineView:(NSOutlineView *)outlineView writeItems:(NSArray *)items toPasteboard:(NSPasteboard *)pboard {
    [self copyNodes:items toPasteboard:pboard];
    return YES;
}

-(NSDragOperation)validateNode:(id)node withChildIndex:(NSInteger)childIndex {
    RNDetailView *detailView = [self detailView];
    [detailView setValidatedDropIndex:childIndex];
    id previousValidatedNode = [[self detailView] validatedDropNode];
    if (previousValidatedNode) {
        NSInteger previousRow = [detailView rowForItem:previousValidatedNode];
        [detailView setNeedsDisplayInRect:[detailView rectOfRow:previousRow]];
    }
    NSInteger currentRow = [detailView rowForItem:node];
    [detailView setNeedsDisplayInRect:[detailView rectOfRow:currentRow]];
    [detailView setValidatedDropNode:node];
    [detailView setNeedsDisplay:YES];
//    NSLog(@"%@", node ? @"move" : @"none");
    if (node)
        return NSDragOperationMove;
    return NSDragOperationNone;
}

-(NSDragOperation)validateNode:(id)node {
    return [self validateNode:node withChildIndex:NSOutlineViewDropOnItemIndex];
}

-(NSDragOperation)outlineView:(NSOutlineView *)outlineView validateDrop:(id <NSDraggingInfo>)draggingInfo proposedItem:(id)dropNode proposedChildIndex:(NSInteger)childIndex {
//    NSLog(@"item: %@ (%@)  index: %i", [[dropNode representedObject] valueForKey:@"title"], [[dropNode representedObject] className], childIndex);
    // TODO: HANDLE CASE OF MOVING BELOW BOTTOM ITEM
    BOOL aisleDrop = [[self rootObject] isKindOfClass:[RNStore class]];
    if (!dropNode && !aisleDrop)
        return [self validateNode:nil];
    id dropItem = [dropNode representedObject];
    if (aisleDrop) {
//        NSLog(@"node: %@  index: %i", dropItem, childIndex);
        if (!dropNode) {
            id topDetailHeader = [[self currentDetailHeaders] objectAtIndex:0];
            NSTreeNode *topNode = [[self detailTreeController] treeNodeForObject:topDetailHeader];
            NSInteger retargetedIndex = childIndex == 0 ? childIndex : (NSInteger)[[topNode childNodes] count];
            [[self detailView] setDropItem:topNode dropChildIndex:retargetedIndex];
            return [self validateNode:topNode withChildIndex:retargetedIndex];
        }
        if (childIndex == NSOutlineViewDropOnItemIndex || ![dropItem isKindOfClass:[RNDetailHeader class]])
            return NSDragOperationNone;
        return [self validateNode:dropNode withChildIndex:childIndex];
//        return childIndex == NSOutlineViewDropOnItemIndex ? NSDragOperationNone : NSDragOperationMove;
    }
    
    // Only allow drops between items for aisles in a store
    if (childIndex != NSOutlineViewDropOnItemIndex) {
            return [self validateNode:nil];
    }
    
    NSDictionary *objectArrays = [self objectArraysFromPasteboard:[draggingInfo draggingPasteboard]];
    if (!objectArrays)
        return [self validateNode:nil];
    
    NSArray *entityNames = [objectArrays allKeys];
    
    if ([dropItem respondsToSelector:@selector(canContainObjectsWithEntityNames:)] && [dropItem canContainObjectsWithEntityNames:entityNames]) 
        return [self validateNode:dropNode];

    NSTreeNode *parentNode = [dropNode parentNode];
    id parent = [parentNode representedObject];
    
    if (![parent respondsToSelector:@selector(canContainObjectsWithEntityNames:)] || ![parent canContainObjectsWithEntityNames:entityNames])
        return [self validateNode:nil];
    
    [outlineView setDropItem:parentNode dropChildIndex:NSOutlineViewDropOnItemIndex];
    return [self validateNode:parentNode];
}

-(void)reorderAislesFromDictionaryArray:(NSArray *)dictionaryArray toPosition:(NSUInteger)aislePosition {
    NSMutableArray *aislesToMove = [NSMutableArray arrayWithCapacity:[dictionaryArray count]];
    for (NSDictionary *dictionaryRepresentation in dictionaryArray) {
        NSURL *objectURI = [dictionaryRepresentation valueForKey:RNDragDropObjectURIKey];
        RNAisle *originalAisle = (RNAisle *)[[self managedObjectContext] objectWithURI:objectURI];
        [aislesToMove addObject:originalAisle];
    }
    [aislesToMove sortUsingDescriptors:[NSArray arrayWithObject:[[NSSortDescriptor alloc] initWithKey:@"aisleNumber" ascending:YES]]];    
    RNStore *store = [[aislesToMove lastObject] store];
    NSMutableArray *allAisles = [[[store aisles] sortedArrayUsingDescriptors:[NSArray arrayWithObject:[[NSSortDescriptor alloc] initWithKey:@"aisleNumber" ascending:YES]]] mutableCopy];
    [allAisles removeObjectsInArray:aislesToMove];
    NSUInteger insertionPoint = aislePosition;
    NSMutableIndexSet *insertIndexes = [NSMutableIndexSet indexSet];
    for (RNAisle *aisle in aislesToMove) {
        if ([[aisle aisleNumber] unsignedIntegerValue] < aislePosition)
            insertionPoint--;
    }
    for (RNAisle *aisle in aislesToMove)
        [insertIndexes addIndex:insertionPoint++];
    [allAisles insertObjects:aislesToMove atIndexes:insertIndexes];
    for (NSUInteger currentAisleIndex = 0; currentAisleIndex < [allAisles count]; currentAisleIndex++) {
        RNAisle *currentAisle = [allAisles objectAtIndex:currentAisleIndex];
        [currentAisle setAisleNumber:[NSNumber numberWithInteger:currentAisleIndex]];
    }
}

-(BOOL)outlineView:(NSOutlineView *)outlineView acceptDrop:(id <NSDraggingInfo>)draggingInfo item:(id)itemNode childIndex:(NSInteger)childIndex {
    NSManagedObject *dropObject = [itemNode representedObject];
    NSPasteboard *pasteboard = [draggingInfo draggingPasteboard];
    NSDictionary *objectArrays = [self objectArraysFromPasteboard:pasteboard];
    if (!objectArrays)
        return NO;
    
    for (NSString *entityName in objectArrays) {
        NSArray *objectArray = [objectArrays valueForKey:entityName];
        if ([[self rootObject] isKindOfClass:[RNStore class]])
            [self reorderAislesFromDictionaryArray:objectArray toPosition:childIndex];
        for (NSDictionary *dictionaryRepresentation in objectArray) {
            NSURL *objectURI = [dictionaryRepresentation valueForKey:RNDragDropObjectURIKey];
            NSManagedObject *originalObject = [[self managedObjectContext] objectWithURI:objectURI];
            [originalObject moveToContainer:dropObject];
        }
    }
    [self validateNode:nil];
    [[self detailTreeController] rearrangeObjects];
    return YES;
}


#pragma mark -
#pragma mark Copy/Paste

-(IBAction)cut:(id)sender {
    [self copy:nil];
    [self deleteSelectedItems:nil];
}

-(IBAction)copy:(id)sender {
    NSArray *selectedNodes = [[self detailTreeController] selectedNodes];
    [self copyNodes:selectedNodes toPasteboard:[NSPasteboard generalPasteboard]];
}

-(IBAction)paste:(id)sender {
    NSDictionary *objectArrays = [self objectArraysFromPasteboard:[NSPasteboard generalPasteboard]];
    if (!objectArrays) {
        NSBeep();
        return;
    }
    NSArray *entityNames = [objectArrays allKeys];

    if (![[self rootObject] canContainObjectsWithEntityNames:entityNames]) {
        NSBeep();
        return;
    }

    for (NSString *entityName in objectArrays) {
        NSArray *objectArray = [objectArrays valueForKey:entityName];
        
        for (NSDictionary *dictionaryRepresentation in objectArray) {
            id destinationObject = nil;
            if ([entityName isEqualToString:RNEntityNameItem] && [[self rootObject] isKindOfClass:[RNList class]]) {
                destinationObject = [NSEntityDescription insertNewObjectForEntityForName:RNEntityNameListItem inManagedObjectContext:[self managedObjectContext]];
                RNItem *sourceItem = (RNItem *)[[self managedObjectContext] objectWithURI:[dictionaryRepresentation valueForKey:RNDragDropObjectURIKey]];
                [destinationObject setItem:sourceItem];
            }
            else if ([entityName isEqualToString:RNEntityNameHistoryItem] && [[self rootObject] isKindOfClass:[RNList class]]) {
                destinationObject = [NSEntityDescription insertNewObjectForEntityForName:RNEntityNameListItem inManagedObjectContext:[self managedObjectContext]];
                RNHistoryItem *sourceHistoryItem = (RNHistoryItem *)[[self managedObjectContext] objectWithURI:[dictionaryRepresentation valueForKey:RNDragDropObjectURIKey]];
                RNItem *sourceItem = [sourceHistoryItem item];
                [destinationObject setCategory:[sourceItem category]];
                [destinationObject setAisles:[sourceItem aisles]];
            }
            else if (![entityName isEqualToString:RNEntityNameAisle] && [[self rootObject] isKindOfClass:[RNStore class]]) {
                NSBeep();
                return;
            }
            else if ([entityName isEqualToString:RNEntityNameListItem] && [[self rootObject] isKindOfClass:[RNList class]]) {
                RNListItem *sourceListItem = (RNListItem *)[[self managedObjectContext] objectWithURI:[dictionaryRepresentation valueForKey:RNDragDropObjectURIKey]];
                RNList *sourceList = [sourceListItem list];
                if (sourceList == [self rootObject]) {
                    NSBeep();
                    return;
                }
            }
            else if ([entityName isEqualToString:RNEntityNameListItem]) {
                destinationObject = (RNListItem *)[[self managedObjectContext] objectWithURI:[dictionaryRepresentation valueForKey:RNDragDropObjectURIKey]];
                [destinationObject moveToContainer:[self rootObject]];
                return;
            }
            else
                destinationObject = [NSEntityDescription insertNewObjectForEntityForName:entityName inManagedObjectContext:[self managedObjectContext]];
            [destinationObject initializeWithDictionary:dictionaryRepresentation];
//            if (![destinationObject isKindOfClass:[RNListItem class]])
//                [destinationObject setTitle:[NSString stringWithFormat:@"%@ - copy", [destinationObject title]]];
            [destinationObject moveToContainer:[self rootObject]];
        }
    }
    [[self detailTreeController] rearrangeObjects];
}

@end
